﻿using System;
using System.Runtime.Caching;
using Framework.Business;

namespace Framework.Helper
{
    public interface IMemoryCacheHelper
    {
        void AddToCache(string key, object value);
        void AddToCache(string key, object value, CacheItemPolicy policy);

        bool TryGet<TData>(string key, out TData data);

        TData GetOrAddObject<TData>(string key, Func<TData> builder) where TData : class;
        TData GetOrAddValue<TData>(string key, Func<TData> builder) where TData : struct;

        TData GetOrAddObject<TData>(string key, CacheItemPolicy policy, Func<TData> builder) where TData : class;
        TData GetOrAddValue<TData>(string key, CacheItemPolicy policy, Func<TData> builder) where TData : struct;

        void ClearCache();
        void Remove(string key);
    }
    public sealed class MemoryCacheHelper : IMemoryCacheHelper, IDisposable
    {
        private static readonly object CacheLock = new object();
        public static CacheItemPolicy DefaultPolicy = new CacheItemPolicy
        {
            SlidingExpiration = new TimeSpan(0, 60, 0)
        };

        private volatile MemoryCache _cache;

        private MemoryCacheHelper()
        { }

        static MemoryCacheHelper()
        {
            InstanceManager.RegisterBuilder<IMemoryCacheHelper, MemoryCacheHelper>(
                () => new MemoryCacheHelper());
        }

        public static IMemoryCacheHelper Instance
        {
            get { return InstanceManager.GetInstance<IMemoryCacheHelper>(); }
        }

        public void AddToCache(string key, object value)
        {
            AddToCache(key, value, DefaultPolicy);
        }

        public void AddToCache(string key, object value, CacheItemPolicy policy)
        {
            lock (CacheLock)
            {
                GetCache().Set(key, value, policy);
            }
        }

        public bool TryGet<TData>(string key, out TData data)
        {
            data = default(TData);

            var cachedData = GetCache().Get(key);

            if (null == cachedData) return false;

            try
            {
                data = (TData)cachedData;
                return true;
            }
            catch
            {
                return false;
            }
        }

        public TData GetOrAddObject<TData>(string key, Func<TData> builder) where TData : class
        {
            return GetOrAddObject(key, DefaultPolicy, builder);
        }

        public TData GetOrAddValue<TData>(string key, Func<TData> builder) where TData : struct
        {
            return GetOrAddValue(key, DefaultPolicy, builder);
        }

        public TData GetOrAddObject<TData>(string key, CacheItemPolicy policy, Func<TData> builder) where TData : class
        {
            var cachedData = GetCache().Get(key) as TData;

            if (null != cachedData) return cachedData;

            lock (CacheLock)
            {
                cachedData = GetCache().Get(key) as TData;

                if (null != cachedData) return cachedData;

                var dataToCache = builder();

                if (null == dataToCache) return default(TData);

                GetCache().Set(key, dataToCache, policy);

                return dataToCache;
            }
        }

        public TData GetOrAddValue<TData>(string key, CacheItemPolicy policy, Func<TData> builder) where TData : struct
        {
            var cachedData = GetCache().Get(key);

            if (null != cachedData) return (TData)cachedData;

            lock (CacheLock)
            {
                cachedData = GetCache().Get(key);

                if (null != cachedData) return (TData)cachedData;

                var dataToCache = builder();

                if (default(TData).Equals(dataToCache)) return default(TData);

                GetCache().Set(key, dataToCache, policy);

                return dataToCache;
            }
        }

        public void Remove(string key)
        {
            lock (CacheLock)
            {
                GetCache().Remove(key);
            }
        }

        public void ClearCache()
        {
            if (_cache == null) return;

            _cache = new MemoryCache(MemoryCacheName);

        }

        public const string MemoryCacheName = "Innomind_MemCache";

        private MemoryCache GetCache()
        {
            if (null != _cache) return _cache;

            lock (CacheLock)
            {
                if (null != _cache) return _cache;
                _cache = new MemoryCache(MemoryCacheName);
            }

            return _cache;
        }

        private bool _disposed;

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        ~MemoryCacheHelper()
        {
            Dispose(false);
        }

        private void Dispose(bool disposing)
        {
            if (_disposed) return;

            if (disposing)
            {
                if (null != _cache) _cache.Dispose();
            }

            _disposed = true;
        }
    }
}
